<?php

// +----------------------------------------------------------------------

// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]

// +----------------------------------------------------------------------

// | Copyright (c) 2006-2013 http://thinkphp.cn All rights reserved.

// +----------------------------------------------------------------------

// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )

// +----------------------------------------------------------------------

// | Author: liu21st <liu21st@gmail.com>

// +----------------------------------------------------------------------



namespace Think\Model;

use Think\Model;

/**

 * 高级模型扩展 

 * @category   Extend

 * @package  Extend

 * @subpackage  Model

 * @author    liu21st <liu21st@gmail.com>

 */

class AdvModel extends Model {

    protected $optimLock        =   'lock_version';

    protected $returnType       =   'array';

    protected $blobFields       =   array();

    protected $blobValues       =   null;

    protected $serializeField   =   array();

    protected $readonlyField    =   array();

    protected $_filter          =   array();

    protected $partition        =   array();



    public function __construct($name='',$tablePrefix='',$connection='') {

        if('' !== $name || is_subclass_of($this,'AdvModel') ){

            // 如果是AdvModel子类或者有传入模型名称则获取字段缓存

        }else{

            // 空的模型 关闭字段缓存

            $this->autoCheckFields = false;

        }

        parent::__construct($name,$tablePrefix,$connection);

    }



    /**

     * 利用__call方法重载 实现一些特殊的Model方法 （魔术方法）

     * @access public

     * @param string $method 方法名称

     * @param mixed $args 调用参数

     * @return mixed

     */

    public function __call($method,$args) {

        if(strtolower(substr($method,0,3))=='top'){

            // 获取前N条记录

            $count = substr($method,3);

            array_unshift($args,$count);

            return call_user_func_array(array(&$this, 'topN'), $args);

        }else{

            return parent::__call($method,$args);

        }

    }



    /**

     * 对保存到数据库的数据进行处理

     * @access protected

     * @param mixed $data 要操作的数据

     * @return boolean

     */

     protected function _facade($data) {

        // 检查序列化字段

        $data = $this->serializeField($data);

        return parent::_facade($data);

     }



    // 查询成功后的回调方法

    protected function _after_find(&$result,$options='') {

        // 检查序列化字段

        $this->checkSerializeField($result);

        // 获取文本字段

        $this->getBlobFields($result);

        // 检查字段过滤

        $result   =  $this->getFilterFields($result);

        // 缓存乐观锁

        $this->cacheLockVersion($result);

    }



    // 查询数据集成功后的回调方法

    protected function _after_select(&$resultSet,$options='') {

        // 检查序列化字段

        $resultSet   =  $this->checkListSerializeField($resultSet);

        // 获取文本字段

        $resultSet   =  $this->getListBlobFields($resultSet);

        // 检查列表字段过滤

        $resultSet   =  $this->getFilterListFields($resultSet);

    }



    // 写入前的回调方法

    protected function _before_insert(&$data,$options='') {

        // 记录乐观锁

        $data = $this->recordLockVersion($data);

        // 检查文本字段

        $data = $this->checkBlobFields($data);

        // 检查字段过滤

        $data = $this->setFilterFields($data);

    }



    protected function _after_insert($data,$options) {

        // 保存文本字段

        $this->saveBlobFields($data);

    }



    // 更新前的回调方法

    protected function _before_update(&$data,$options='') {

        // 检查乐观锁

        if(!$this->checkLockVersion($data,$options)) {

            return false;

        }

        // 检查文本字段

        $data = $this->checkBlobFields($data);

        // 检查只读字段

        $data = $this->checkReadonlyField($data);

        // 检查字段过滤

        $data = $this->setFilterFields($data);

    }



    protected function _after_update($data,$options) {

        // 保存文本字段

        $this->saveBlobFields($data);

    }



    protected function _after_delete($data,$options) {

        // 删除Blob数据

        $this->delBlobFields($data);

    }



    /**

     * 记录乐观锁

     * @access protected

     * @param array $data 数据对象

     * @return array

     */

    protected function recordLockVersion($data) {

        // 记录乐观锁

        if($this->optimLock && !isset($data[$this->optimLock]) ) {

            if(in_array($this->optimLock,$this->fields,true)) {

                $data[$this->optimLock]  =   0;

            }

        }

        return $data;

    }



    /**

     * 缓存乐观锁

     * @access protected

     * @param array $data 数据对象

     * @return void

     */

    protected function cacheLockVersion($data) {

        if($this->optimLock) {

            if(isset($data[$this->optimLock]) && isset($data[$this->getPk()])) {

                // 只有当存在乐观锁字段和主键有值的时候才记录乐观锁

                $_SESSION[$this->name.'_'.$data[$this->getPk()].'_lock_version']    =   $data[$this->optimLock];

            }

        }

    }



    /**

     * 检查乐观锁

     * @access protected

     * @param array $data  当前数据

     * @param array $options 查询表达式

     * @return mixed

     */

    protected function checkLockVersion(&$data,$options) {

        $id = $data[$this->getPk()];

        // 检查乐观锁

        $identify   = $this->name.'_'.$id.'_lock_version';

        if($this->optimLock && isset($_SESSION[$identify])) {

            $lock_version = $_SESSION[$identify];

            $vo   =  $this->field($this->optimLock)->find($id);

            $_SESSION[$identify]     =   $lock_version;

            $curr_version = $vo[$this->optimLock];

            if(isset($curr_version)) {

                if($curr_version>0 && $lock_version != $curr_version) {

                    // 记录已经更新

                    $this->error = L('_RECORD_HAS_UPDATE_');

                    return false;

                }else{

                    // 更新乐观锁

                    $save_version = $data[$this->optimLock];

                    if($save_version != $lock_version+1) {

                        $data[$this->optimLock]  =   $lock_version+1;

                    }

                    $_SESSION[$identify]     =   $lock_version+1;

                }

            }

        }

        return true;

    }



    /**

     * 查找前N个记录

     * @access public

     * @param integer $count 记录个数

     * @param array $options 查询表达式

     * @return array

     */

    public function topN($count,$options=array()) {

        $options['limit'] =  $count;

        return $this->select($options);

    }



    /**

     * 查询符合条件的第N条记录

     * 0 表示第一条记录 -1 表示最后一条记录

     * @access public

     * @param integer $position 记录位置

     * @param array $options 查询表达式

     * @return mixed

     */

    public function getN($position=0,$options=array()) {

        if($position>=0) { // 正向查找

            $options['limit'] = $position.',1';

            $list   =  $this->select($options);

            return $list?$list[0]:false;

        }else{ // 逆序查找

            $list   =  $this->select($options);

            return $list?$list[count($list)-abs($position)]:false;

        }

    }



    /**

     * 获取满足条件的第一条记录

     * @access public

     * @param array $options 查询表达式

     * @return mixed

     */

    public function first($options=array()) {

        return $this->getN(0,$options);

    }



    /**

     * 获取满足条件的最后一条记录

     * @access public

     * @param array $options 查询表达式

     * @return mixed

     */

    public function last($options=array()) {

        return $this->getN(-1,$options);

    }



    /**

     * 返回数据

     * @access public

     * @param array $data 数据

     * @param string $type 返回类型 默认为数组

     * @return mixed

     */

    public function returnResult($data,$type='') {

        if('' === $type)

            $type = $this->returnType;

        switch($type) {

            case 'array' :  return $data;

            case 'object':  return (object)$data;

            default:// 允许用户自定义返回类型

                if(class_exists($type))

                    return new $type($data);

                else

                    E(L('_CLASS_NOT_EXIST_').':'.$type);

        }

    }



    /**

     * 获取数据的时候过滤数据字段

     * @access protected

     * @param mixed $result 查询的数据

     * @return array

     */

    protected function getFilterFields(&$result) {

        if(!empty($this->_filter)) {

            foreach ($this->_filter as $field=>$filter){

                if(isset($result[$field])) {

                    $fun  =  $filter[1];

                    if(!empty($fun)) {

                        if(isset($filter[2]) && $filter[2]){

                            // 传递整个数据对象作为参数

                            $result[$field]  =  call_user_func($fun,$result);

                        }else{

                            // 传递字段的值作为参数

                            $result[$field]  =  call_user_func($fun,$result[$field]);

                        }

                    }

                }

            }

        }

        return $result;

    }



    protected function getFilterListFields(&$resultSet) {

        if(!empty($this->_filter)) {

            foreach ($resultSet as $key=>$result)

                $resultSet[$key]  =  $this->getFilterFields($result);

        }

        return $resultSet;

    }



    /**

     * 写入数据的时候过滤数据字段

     * @access protected

     * @param mixed $result 查询的数据

     * @return array

     */

    protected function setFilterFields($data) {

        if(!empty($this->_filter)) {

            foreach ($this->_filter as $field=>$filter){

                if(isset($data[$field])) {

                    $fun              =  $filter[0];

                    if(!empty($fun)) {

                        if(isset($filter[2]) && $filter[2]) {

                            // 传递整个数据对象作为参数

                            $data[$field]   =  call_user_func($fun,$data);

                        }else{

                            // 传递字段的值作为参数

                            $data[$field]   =  call_user_func($fun,$data[$field]);

                        }

                    }

                }

            }

        }

        return $data;

    }



    /**

     * 返回数据列表

     * @access protected

     * @param array $resultSet 数据

     * @param string $type 返回类型 默认为数组

     * @return void

     */

    protected function returnResultSet(&$resultSet,$type='') {

        foreach ($resultSet as $key=>$data)

            $resultSet[$key]  =  $this->returnResult($data,$type);

        return $resultSet;

    }



    protected function checkBlobFields(&$data) {

        // 检查Blob文件保存字段

        if(!empty($this->blobFields)) {

            foreach ($this->blobFields as $field){

                if(isset($data[$field])) {

                    if(isset($data[$this->getPk()]))

                        $this->blobValues[$this->name.'/'.$data[$this->getPk()].'_'.$field] =   $data[$field];

                    else

                        $this->blobValues[$this->name.'/@?id@_'.$field] =   $data[$field];

                    unset($data[$field]);

                }

            }

        }

        return $data;

    }



    /**

     * 获取数据集的文本字段

     * @access protected

     * @param mixed $resultSet 查询的数据

     * @param string $field 查询的字段

     * @return void

     */

    protected function getListBlobFields(&$resultSet,$field='') {

        if(!empty($this->blobFields)) {

            foreach ($resultSet as $key=>$result){

                $result =   $this->getBlobFields($result,$field);

                $resultSet[$key]    =   $result;

            }

        }

        return $resultSet;

    }



    /**

     * 获取数据的文本字段

     * @access protected

     * @param mixed $data 查询的数据

     * @param string $field 查询的字段

     * @return void

     */

    protected function getBlobFields(&$data,$field='') {

        if(!empty($this->blobFields)) {

            $pk =   $this->getPk();

            $id =   $data[$pk];

            if(empty($field)) {

                foreach ($this->blobFields as $field){

                    $identify   =   $this->name.'/'.$id.'_'.$field;

                    $data[$field]   =   F($identify);

                }

                return $data;

            }else{

                $identify   =   $this->name.'/'.$id.'_'.$field;

                return F($identify);

            }

        }

    }



    /**

     * 保存File方式的字段

     * @access protected

     * @param mixed $data 保存的数据

     * @return void

     */

    protected function saveBlobFields(&$data) {

        if(!empty($this->blobFields)) {

            foreach ($this->blobValues as $key=>$val){

                if(strpos($key,'@?id@'))

                    $key    =   str_replace('@?id@',$data[$this->getPk()],$key);

                F($key,$val);

            }

        }

    }



    /**

     * 删除File方式的字段

     * @access protected

     * @param mixed $data 保存的数据

     * @param string $field 查询的字段

     * @return void

     */

    protected function delBlobFields(&$data,$field='') {

        if(!empty($this->blobFields)) {

            $pk =   $this->getPk();

            $id =   $data[$pk];

            if(empty($field)) {

                foreach ($this->blobFields as $field){

                    $identify   =   $this->name.'/'.$id.'_'.$field;

                    F($identify,null);

                }

            }else{

                $identify   =   $this->name.'/'.$id.'_'.$field;

                F($identify,null);

            }

        }

    }



    /**

     * 字段值延迟增长

     * @access public

     * @param string $field  字段名

     * @param integer $step  增长值

     * @param integer $lazyTime  延时时间(s)

     * @return boolean

     */

    public function setLazyInc($field,$step=1,$lazyTime=0) {

        $condition   =  $this->options['where'];

        if(empty($condition)) { // 没有条件不做任何更新

            return false;

        }

        if($lazyTime>0) {// 延迟写入

            $guid =  md5($this->name.'_'.$field.'_'.serialize($condition));

            $step = $this->lazyWrite($guid,$step,$lazyTime);

            if(false === $step ) return true; // 等待下次写入

        }

        return $this->setField($field,array('exp',$field.'+'.$step));

    }



    /**

     * 字段值延迟减少

     * @access public

     * @param string $field  字段名

     * @param integer $step  减少值

     * @param integer $lazyTime  延时时间(s)

     * @return boolean

     */

    public function setLazyDec($field,$step=1,$lazyTime=0) {

        $condition   =  $this->options['where'];

        if(empty($condition)) { // 没有条件不做任何更新

            return false;

        }

        if($lazyTime>0) {// 延迟写入

            $guid =  md5($this->name.'_'.$field.'_'.serialize($condition));

            $step = $this->lazyWrite($guid,$step,$lazyTime);

            if(false === $step ) return true; // 等待下次写入

        }

        return $this->setField($field,array('exp',$field.'-'.$step));

    }



    /**

     * 延时更新检查 返回false表示需要延时

     * 否则返回实际写入的数值

     * @access public

     * @param string $guid  写入标识

     * @param integer $step  写入步进值

     * @param integer $lazyTime  延时时间(s)

     * @return false|integer

     */

    protected function lazyWrite($guid,$step,$lazyTime) {

        if(false !== ($value = F($guid))) { // 存在缓存写入数据

            if(time()>F($guid.'_time')+$lazyTime) {

                // 延时更新时间到了，删除缓存数据 并实际写入数据库

                F($guid,NULL);

                F($guid.'_time',NULL);

                return $value+$step;

            }else{

                // 追加数据到缓存

                F($guid,$value+$step);

                return false;

            }

        }else{ // 没有缓存数据

            F($guid,$step);

            // 计时开始

            F($guid.'_time',time());

            return false;

        }

    }



    /**

     * 检查序列化数据字段

     * @access protected

     * @param array $data 数据

     * @return array

     */

     protected function serializeField(&$data) {

        // 检查序列化字段

        if(!empty($this->serializeField)) {

            // 定义方式  $this->serializeField = array('ser'=>array('name','email'));

            foreach ($this->serializeField as $key=>$val){

                if(empty($data[$key])) {

                    $serialize  =   array();

                    foreach ($val as $name){

                        if(isset($data[$name])) {

                            $serialize[$name]   =   $data[$name];

                            unset($data[$name]);

                        }

                    }

                    if(!empty($serialize)) {

                        $data[$key] =   serialize($serialize);

                    }

                }

            }

        }

        return $data;

     }



    // 检查返回数据的序列化字段

    protected function checkSerializeField(&$result) {

        // 检查序列化字段

        if(!empty($this->serializeField)) {

            foreach ($this->serializeField as $key=>$val){

                if(isset($result[$key])) {

                    $serialize   =   unserialize($result[$key]);

                    foreach ($serialize as $name=>$value)

                        $result[$name]  =   $value;

                    unset($serialize,$result[$key]);

                }

            }

        }

        return $result;

    }



    // 检查数据集的序列化字段

    protected function checkListSerializeField(&$resultSet) {

        // 检查序列化字段

        if(!empty($this->serializeField)) {

            foreach ($this->serializeField as $key=>$val){

                foreach ($resultSet as $k=>$result){

                    if(isset($result[$key])) {

                        $serialize   =   unserialize($result[$key]);

                        foreach ($serialize as $name=>$value)

                            $result[$name]  =   $value;

                        unset($serialize,$result[$key]);

                        $resultSet[$k] =   $result;

                    }

                }

            }

        }

        return $resultSet;

    }



    /**

     * 检查只读字段

     * @access protected

     * @param array $data 数据

     * @return array

     */

    protected function checkReadonlyField(&$data) {

        if(!empty($this->readonlyField)) {

            foreach ($this->readonlyField as $key=>$field){

                if(isset($data[$field]))

                    unset($data[$field]);

            }

        }

        return $data;

    }



    /**

     * 批处理执行SQL语句

     * 批处理的指令都认为是execute操作

     * @access public

     * @param array $sql  SQL批处理指令

     * @return boolean

     */

    public function patchQuery($sql=array()) {

        if(!is_array($sql)) return false;

        // 自动启动事务支持

        $this->startTrans();

        try{

            foreach ($sql as $_sql){

                $result   =  $this->execute($_sql);

                if(false === $result) {

                    // 发生错误自动回滚事务

                    $this->rollback();

                    return false;

                }

            }

            // 提交事务

            $this->commit();

        } catch (ThinkException $e) {

            $this->rollback();

        }

        return true;

    }



    /**

     * 得到分表的的数据表名

     * @access public

     * @param array $data 操作的数据

     * @return string

     */

    public function getPartitionTableName($data=array()) {

        // 对数据表进行分区

        if(isset($data[$this->partition['field']])) {

            $field   =   $data[$this->partition['field']];

            switch($this->partition['type']) {

                case 'id':

                    // 按照id范围分表

                    $step    =   $this->partition['expr'];

                    $seq    =   floor($field / $step)+1;

                    break;

                case 'year':

                    // 按照年份分表

                    if(!is_numeric($field)) {

                        $field   =   strtotime($field);

                    }

                    $seq    =   date('Y',$field)-$this->partition['expr']+1;

                    break;

                case 'mod':

                    // 按照id的模数分表

                    $seq    =   ($field % $this->partition['num'])+1;

                    break;

                case 'md5':

                    // 按照md5的序列分表

                    $seq    =   (ord(substr(md5($field),0,1)) % $this->partition['num'])+1;

                    break;

                default :

                    if(function_exists($this->partition['type'])) {

                        // 支持指定函数哈希

                        $fun    =   $this->partition['type'];

                        $seq    =   (ord(substr($fun($field),0,1)) % $this->partition['num'])+1;

                    }else{

                        // 按照字段的首字母的值分表

                        $seq    =   (ord($field{0}) % $this->partition['num'])+1;

                    }

            }

            return $this->getTableName().'_'.$seq;

        }else{

            // 当设置的分表字段不在查询条件或者数据中

            // 进行联合查询，必须设定 partition['num']

            $tableName  =   array();

            for($i=0;$i<$this->partition['num'];$i++)

                $tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.($i+1);

            $tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name;

            return $tableName;

        }

    }

}